﻿// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Text;
using System.Threading;

using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Text;

namespace Generators
{
    public partial class EventSourceGenerator
    {
        /// <summary>Code for a [GeneratedCode] attribute to put on the top-level generated members.</summary>
        private static readonly string s_generatedCodeAttribute = $"[global::System.CodeDom.Compiler.GeneratedCodeAttribute(\"{typeof(EventSourceGenerator).Assembly.GetName().Name}\", \"{typeof(EventSourceGenerator).Assembly.GetName().Version}\")]";

        private static void EmitSourceFile(SourceProductionContext context, EventSourceClass ec)
        {
            StringBuilder sb = new StringBuilder(1024);

            sb.AppendLine(@"// <auto-generated/>");
            sb.AppendLine();
            sb.AppendLine("using System;");
            GenType(ec, sb);

            context.AddSource($"{ec.ClassName}.g.cs", SourceText.From(sb.ToString(), Encoding.UTF8));
        }

        private static void GenType(EventSourceClass ec, StringBuilder sb)
        {
            if (!string.IsNullOrWhiteSpace(ec.Namespace))
            {
                sb.AppendLine($@"
namespace {ec.Namespace}
{{");
            }

            sb.AppendLine($@"
    {s_generatedCodeAttribute}
    partial class {ec.ClassName}
    {{");
            GenerateConstructor(ec, sb);

            GenerateProviderMetadata(ec.SourceName, sb);

            sb.AppendLine($@"
    }}");

            if (!string.IsNullOrWhiteSpace(ec.Namespace))
            {
                sb.AppendLine($@"
}}");
            }
        }

        private static void GenerateConstructor(EventSourceClass ec, StringBuilder sb)
        {
            sb.AppendLine($@"
        private {ec.ClassName}() : base(new Guid({ec.Guid.ToString("x").Replace("{", "").Replace("}", "")}), ""{ec.SourceName}"") {{ }}");
        }

        private static void GenerateProviderMetadata(string sourceName, StringBuilder sb)
        {
            sb.Append(@"
        private protected override ReadOnlySpan<byte> ProviderMetadata => new byte[] { ");

            byte[] metadataBytes = MetadataForString(sourceName);
            foreach (byte b in metadataBytes)
            {
                sb.Append($"0x{b:x}, ");
            }

            sb.AppendLine(@"};");
        }

        // From System.Private.CoreLib
        private static byte[] MetadataForString(string name)
        {
            CheckName(name);
            int metadataSize = Encoding.UTF8.GetByteCount(name) + 3;
            byte[]? metadata = new byte[metadataSize];
            ushort totalSize = checked((ushort)(metadataSize));
            metadata[0] = unchecked((byte)totalSize);
            metadata[1] = unchecked((byte)(totalSize >> 8));
            Encoding.UTF8.GetBytes(name, 0, name.Length, metadata, 2);
            return metadata;
        }

        private static void CheckName(string? name)
        {
            if (name != null && 0 <= name.IndexOf('\0'))
            {
                throw new ArgumentOutOfRangeException(nameof(name));
            }
        }
    }
}
